package org.budo.graph.service.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.budo.graph.Constant;
import org.budo.support.freemarker.FreemarkerUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.servlet.util.QueryStringUtil;
import org.budo.support.spring.io.util.ResourceUtil;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.impl.StaticLoggerBinder;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import lombok.extern.slf4j.Slf4j;

/**
 * @author limingwei
 * @date 2016年6月14日 下午8:39:48
 * 
 * @see https://blog.csdn.net/kyfxbl/article/details/11966543
 */
@Slf4j
public class LogLevelUtil {
    private static final Boolean HAS_LOG4J = ReflectUtil.hasClass("org.apache.log4j.LogManager");

    private static final boolean HAS_SLF4J = ReflectUtil.hasClass("org.slf4j.ILoggerFactory");

    private static final String LOG_LEVELS_KEY = "logLevels";

    private static final String DUBBO_DRUID_FM_LOG_LEVEL_HTM = Constant.GRAPH_TEMPLATE_PATH + "/log-level.htm";

    private static final Comparator<Map<String, Object>> COMPARATOR = new Comparator<Map<String, Object>>() {
        public int compare(Map<String, Object> o1, Map<String, Object> o2) {
            String logger1 = (String) o1.get("logger");
            String logger2 = (String) o2.get("logger");

            String type1 = (String) o1.get("type");
            String type2 = (String) o2.get("type");

            int typeCompare = type1.compareTo(type2);
            if (0 != typeCompare) {
                return typeCompare;
            }

            return logger1.compareTo(logger2);
        }
    };

    // TODO 第一次时记录原始logger，可供恢复
    public static String writeLogLevelResponse(String url) {
        log.info("#60 logbackLevelList info, isDebugEnabled=" + log.isDebugEnabled());
        log.debug("#61 logbackLevelList debug, isDebugEnabled=" + log.isDebugEnabled());

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(url);
        String updateLogger = parameters.get("update_logger");
        String updateLevel = parameters.get("update_level");

        if (null != updateLogger && !updateLogger.trim().isEmpty() && null != updateLevel && !updateLevel.trim().isEmpty()) {
            _updateLog4jLevel(updateLogger, updateLevel);
            _updateLogbackLevel(updateLogger, updateLevel);
        }

        List<Map<String, Object>> logLevelList = _logLevelList();

        String inputString = ResourceUtil.classPathResourceInputStreamToString(DUBBO_DRUID_FM_LOG_LEVEL_HTM);

        Collections.sort(logLevelList, COMPARATOR);

        return FreemarkerUtil.freemarkerRender(inputString, MapUtil.stringObjectMap(LOG_LEVELS_KEY, logLevelList));
    }

    private static void _updateLog4jLevel(String loggerName, String level) {
        if (!HAS_LOG4J) {
            log.warn("#88 _updateLog4jLevel, not HAS_LOG4J, loggerName=" + loggerName + ", level=" + level);
            return;
        }

        if (StringUtil.equalsIgnoreCase("root", loggerName)) { // 更新 root logger 的级别，会更新所有logger的级别
            org.apache.log4j.Logger log4jRootLogger = LogManager.getRootLogger();

            Level oldLevel = log4jRootLogger.getLevel();
            Level newLevel = Level.toLevel(level.toUpperCase());
            if (!StringUtil.equalsIgnoreCase(newLevel + "", oldLevel + "")) {
                log.warn("#88 changing log level, loggerName=" + loggerName + ", log4j.name=" + log4jRootLogger.getName() + ", level, " + oldLevel + "->" + newLevel);
                log4jRootLogger.setLevel(newLevel);
            }
        }

        // 更新root以外的其他logger 的级别
        Enumeration enumeration = LogManager.getCurrentLoggers();
        while (enumeration.hasMoreElements()) {
            org.apache.log4j.Logger log4jLogger = (org.apache.log4j.Logger) enumeration.nextElement();
            if (StringUtil.equalsIgnoreCase("root", loggerName) // 指定 root 会更新所有
                    || StringUtil.startsWith(log4jLogger.getName(), loggerName)) { // 更新上级会同时更新下级会同时更新

                Level oldLevel = log4jLogger.getLevel();
                Level newLevel = Level.toLevel(level);
                if (!StringUtil.equalsIgnoreCase(newLevel + "", oldLevel + "")) {
                    log.warn("#103 changing log level, loggerName=" + loggerName + ", log4j.name=" + log4jLogger.getName() + ", level, " + oldLevel + "->" + newLevel);
                    log4jLogger.setLevel(newLevel);
                }
            }
        }
    }

    private static void _updateLogbackLevel(String loggerName, String level) {
        if (!HAS_SLF4J) {
            log.warn("#122 _updateLogbackLevel, not HAS_SLF4J, loggerName=" + loggerName + ", level=" + level);
            return;
        }

        ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
        if (null == loggerFactory || !(loggerFactory instanceof LoggerContext)) {
            log.warn("#113 StaticLoggerBinder.loggerFactory is not logback.LoggerContext");
            return;
        }

        LoggerContext loggerContext = (LoggerContext) loggerFactory;
        List<ch.qos.logback.classic.Logger> loggers = loggerContext.getLoggerList();
        for (Logger logger : loggers) {
            if (!(logger instanceof ch.qos.logback.classic.Logger)) {
                log.warn("#121 logger is not logback, logger=" + logger);
            }

            ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;
            if (StringUtil.equalsIgnoreCase("root", loggerName) // 指定 root 会更新所有
                    || StringUtil.startsWith(logbackLogger.getName(), loggerName)) { // 更新上级会同时更新下级会同时更新

                ch.qos.logback.classic.Level oldLevel = logbackLogger.getEffectiveLevel();
                ch.qos.logback.classic.Level newLevel = ch.qos.logback.classic.Level.toLevel(level);
                if (!StringUtil.equalsIgnoreCase(newLevel + "", oldLevel + "")) {
                    logbackLogger.setLevel(newLevel);

                    log.warn("#131 changing log level, loggerName=" + loggerName //
                            + ", logback.name=" + logbackLogger.getName() //
                            + ", level, " + oldLevel + "->" + newLevel);
                }
            }
        }
    }

    private static List<Map<String, Object>> _logLevelList() {
        List<Map<String, Object>> logLevelList = new ArrayList<Map<String, Object>>();

        Map<String, Object> log4jRootLevel = log4jRootLevel();
        ListUtil.add(logLevelList, log4jRootLevel);

        List<Map<String, Object>> logbackLevelList = logbackLevelList();
        logLevelList.addAll(logbackLevelList);

        List<Map<String, Object>> log4jLevelList = log4jLevelList();
        logLevelList.addAll(log4jLevelList);
        return logLevelList;
    }

    private static Map<String, Object> log4jRootLevel() {
        if (!HAS_LOG4J) {
            log.warn("#162 log4jLevelList, no log4j");
            return null;
        }

        org.apache.log4j.Logger log4jRootLogger = LogManager.getRootLogger();

        return MapUtil.stringObjectMap("type", "log4j", //
                "logger", log4jRootLogger.getName(), //
                "level", log4jRootLogger.getLevel(), //
                "additivity", log4jRootLogger.getAdditivity(), //
                "appenderNames", getLog4jAppenderNames(log4jRootLogger));
    }

    private static List<Map<String, Object>> log4jLevelList() {
        if (!HAS_LOG4J) {
            log.warn("#176 log4jLevelList, no log4j");
            return new ArrayList<>();
        }

        List<Map<String, Object>> logLevelList = new ArrayList<Map<String, Object>>();
        Enumeration enumeration = LogManager.getCurrentLoggers();
        while (enumeration.hasMoreElements()) {
            org.apache.log4j.Logger log4jLogger = (org.apache.log4j.Logger) enumeration.nextElement();
            String log4jLoggerName = log4jLogger.getName();
            Level log4jLoggerLevel = log4jLogger.getLevel();
            if (null != log4jLoggerLevel) {
                logLevelList.add(MapUtil.stringObjectMap("type", "log4j", //
                        "logger", log4jLoggerName, //
                        "level", log4jLoggerLevel, //
                        "additivity", log4jLogger.getAdditivity(), //
                        "appenderNames", getLog4jAppenderNames(log4jLogger)));
            }
        }

        return logLevelList;
    }

    private static List<Map<String, Object>> logbackLevelList() {
        List<Map<String, Object>> logLevelList = new ArrayList<Map<String, Object>>();

        if (!HAS_SLF4J) {
            log.warn("#201 logbackLevelList, no slf4j");
            return logLevelList;
        }

        ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();

        if (null == loggerFactory || !(loggerFactory instanceof LoggerContext)) {
            log.warn("#100 StaticLoggerBinder.loggerFactory is not logback.LoggerContext");
            return logLevelList;
        }

        LoggerContext loggerContext = (LoggerContext) loggerFactory;
        List<ch.qos.logback.classic.Logger> loggers = loggerContext.getLoggerList();
        for (Logger logger : loggers) {
            if (!(logger instanceof ch.qos.logback.classic.Logger)) {
                log.warn("#106 logger is not logback, logger=" + logger);
            }

            ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;

            logLevelList.add(MapUtil.stringObjectMap("type", "logback", //
                    "logger", logbackLogger.getName(), //
                    "level", logbackLogger.getEffectiveLevel(), //
                    "additivity", logbackLogger.isAdditive(), //
                    "appenderNames", getLogbackAppenderNames(logbackLogger)));
        }

        return logLevelList;
    }

    private static List<String> getLogbackAppenderNames(ch.qos.logback.classic.Logger logbackLogger) {
        List<String> appenderNames = new ArrayList<String>();
        Iterator<ch.qos.logback.core.Appender<ILoggingEvent>> iterator = logbackLogger.iteratorForAppenders();
        while (iterator.hasNext()) {
            ch.qos.logback.core.Appender<ILoggingEvent> appender = iterator.next();
            String name = appender.getName();
            appenderNames.add(name);
        }
        return appenderNames;
    }

    private static List<String> getLog4jAppenderNames(org.apache.log4j.Logger log4jLogger) {
        List<String> appenderNames = new ArrayList<String>();
        Enumeration loggerAppenders = log4jLogger.getAllAppenders();
        while (loggerAppenders.hasMoreElements()) {
            Appender loggerAppender = (Appender) loggerAppenders.nextElement();
            String name = loggerAppender.getName();
            appenderNames.add(name);
        }
        return appenderNames;
    }
}